package minidb.record; 

import minidb.type.*;
import minidb.buffer.*;
import static minidb.type.Const.*;

public class RecordPage {
	private int slot;
	private int pageNum;
	private int pos;
	private int startPage;
	private java.util.ArrayList<fieldinfo> tableInfo;
	private TableInfo tab;
	
	public RecordPage(TableInfo tableinfo){
		this.slot = tableinfo.recordlength()+1;
		this.startPage = this.pageNum = tableinfo.startpage();
		this.pos = INT_SIZE * 4;
		tableInfo = new java.util.ArrayList<fieldinfo>();
		for(fieldinfo tmp : tableinfo.schema().values()){
			int i;
			for(i = 0; i < tableInfo.size(); i++){
				if(tmp.position() < tableInfo.get(i).position())
					break;
			}
			tableInfo.add(i, tmp);
		}
		this.tab = tableinfo;
	}
	
	public java.util.ArrayList<fieldinfo> fieldinfo(){ return this.tableInfo; }
	
	public void rewind(){
		this.pageNum = this.startPage;
		this.pos = INT_SIZE * 5;
	}
	
	public void beforeFirst(){
		this.pos = INT_SIZE * 5;
	}
	
	public int pos(){
		return this.pos;
	}
	
	public void pos(int pos){
		this.pos = pos;
	}
	private boolean nextPage(){
		Buffer buf = bufpool.pin(pageNum);
		int nextPage = buf.getInt(0);
		bufpool.unpin(buf);
		if(nextPage == 0){	
			return false;
		}
		pageNum = nextPage;
		this.pos = INT_SIZE * 5;
		return true;
	}
	
	public boolean prevPage(){
		Buffer buf = bufpool.pin(pageNum);
		int prePage = buf.getInt(INT_SIZE*2);
		bufpool.unpin(buf);
		if(prePage == 0)
			return false;
		pageNum = prePage;
		this.pos = INT_SIZE * 5;
		return true;
	}
	
	private void newPage(){
		Buffer buf = bufpool.pin(pageNum);
		int oldNext = buf.getInt(NEXT_PAGE);
		int newPage = dbFile.allocPage();
		int oldPage = buf.getInt(CURRENT_PAGE);
		buf.putInt(NEXT_PAGE, newPage);
		bufpool.unpin(buf);
		buf = bufpool.pin(startPage);
		buf.putInt(FREEPAGE, newPage);
		bufpool.unpin(buf);
		buf = bufpool.pin(newPage);
		this.pos = INT_SIZE * 5;
		pageNum = newPage;
		buf.putInt(NEXT_PAGE, oldNext);
		buf.putInt(PRE_PAGE, oldPage);
		buf.putInt(FREEPAGE, 0);
		buf.putInt(FREE, INT_SIZE * 5);
		bufpool.unpin(buf);
	}

	public boolean isvalid(){
		Buffer buf = bufpool.pin(pageNum);
		if((int)buf.get(pos)==0){
			bufpool.unpin(buf);
			return false;
		}
		bufpool.unpin(buf);
		return true;
	}
	
	public boolean next(){
		synchronized(tab.lock){
			tab.reading++;
		}
		while(!isvalid()){
			pos += slot;
			if(pos >= PAGE_SIZE){
				if(!nextPage()){
					synchronized(tab.lock){
						tab.reading--;
					}
					return false;
				}
				synchronized(tab.lock){
					tab.reading--;
				}
				return next();
			}
		}
		synchronized(tab.lock){
			tab.reading--;
		}
		return true;
	}
	
	public RID Rid(){
		return new RID(this.pageNum,this.pos);
	}
	
	public Record get(RID rid){
		Rid(rid);
		return get();
	}
	
	public boolean Rid(RID r){
		if((r.pageNum()+1) > dbFile.size())
			return false;
		this.pageNum = r.pageNum();
		this.pos = r.rid();
		return true;
	}
	
	public RID getRid(){
		pos+=slot;
		return new RID(this.pageNum, pos-slot);
	}
	
	public Record get(){
		synchronized(tab.lock){
			tab.reading++;
		}
		if(isvalid()){
			RID r = new RID(this.pageNum,pos);
			pos+=slot;
			Buffer buf = bufpool.pin(pageNum);
			Record result = new Record(r, slot-1, tableInfo, buf);
			bufpool.unpin(buf);
			synchronized(tab.lock){
				tab.reading--;
			}
			return result;
		}
		synchronized(tab.lock){
			tab.reading--;
		}
		return null;
	}
	
	public RID Insert(Record record){
		int freePage = 0;
		int newpos = 0;
		int newFree = 0;
		RID r;
		this.pageNum = startPage;
		while(true){
			synchronized(tab.lock){
				if(tab.reading==0){
					Buffer buf = bufpool.pin(pageNum);
					freePage = buf.getInt(FREEPAGE);
					newpos = buf.getInt(FREE);
					bufpool.unpin(buf);
					if(newpos+slot > PAGE_SIZE){
						if(freePage == 0){
							newPage();
							newpos = INT_SIZE * 5;
							newFree = INT_SIZE * 5 + slot;
						}
						else{
							buf = bufpool.pin(freePage);
							pageNum = freePage;
							newpos = buf.getInt(FREE);
							newFree = buf.getInt(newpos);
							bufpool.unpin(buf);
						}
					}
					else{
						buf = bufpool.pin(pageNum);
						newFree = buf.getInt(newpos);
						bufpool.unpin(buf);
					}
					buf = bufpool.pin(pageNum);
					buf.put(newpos, (byte)1);
					buf.putByte(newpos+1, record.record(), slot-1);
					r = new RID(buf.pageNum(), newpos);
					if(newFree == 0){
						newFree = newpos + slot;
						if(newFree+slot >= 1024){
							freePage = buf.getInt(FREEPAGE);
							buf.putInt(FREE, PAGE_SIZE);
							buf.putInt(FREEPAGE, 0);
							bufpool.unpin(buf);
							buf = bufpool.pin(startPage);
							buf.putInt(FREEPAGE, freePage);
						}
						else{
							buf.putInt(FREE, newFree);
						}
					}
					else {
						buf.putInt(FREE, newFree);
						if(newFree == PAGE_SIZE){
							freePage = buf.getInt(FREEPAGE);
							bufpool.unpin(buf);
							buf = bufpool.pin(startPage);
							buf.putInt(FREEPAGE, freePage);
						}
					}
					bufpool.unpin(buf);
					break;
				}
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return r;
	}
	
	public void delete(RID r){
		int free;
		int freePage;
		if(r.pageNum()==139)
			System.out.println("");
		if((r.pageNum()+1) > dbFile.size())
			throw new RuntimeException("Invalid Page Number: "+r.pageNum());
		this.pos = r.rid();
		this.pageNum = r.pageNum();
		Buffer buf = bufpool.pin(pageNum);
		while(true){
			synchronized(tab.lock){
				if(tab.reading==0){
					free = buf.getInt(FREE);
					buf.putInt(pos, free);
					buf.putInt(FREE, pos);
					if(free == PAGE_SIZE){
						bufpool.unpin(buf);
						buf = bufpool.pin(startPage);
						freePage = buf.getInt(FREEPAGE);
						buf.putInt(FREEPAGE, r.pageNum());
						bufpool.unpin(buf);
						buf = bufpool.pin(r.pageNum());
						buf.putInt(FREEPAGE, freePage);		
					}
					bufpool.unpin(buf);
					break;
				}
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void update(Record record){
		RID r = record.rid();
		this.pos = r.rid();
		this.pageNum = r.pageNum();
		while(true){
			synchronized(tab.lock){
				if(tab.reading==0){
					Buffer buf = bufpool.pin(pageNum);
					buf.putByte(pos+1, record.record(), slot-1);
					bufpool.unpin(buf);
					break;
				}
			}
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void close(){
		;
	}
}
